unit cx_currencies;

interface
uses
 Classes;

type
 TERIniData = record
  //ExchangeRates section
  PluginVersion: String;
  SourceName: String;
  NativeCurr: String;
  ICU: Boolean;
  DateFormat: String;
  //CurrencyCodes section
  CC_URL: String;
  CC_Root: String;
  CC_Node: String;
  CC_ICT: Boolean;
  CC_ICN: String;
  NativeName: String;
  EngName: String;
  //Daily section
  D_URL: String;
  DateKey: String;
  //Dynamic section
  Dyn_URL: String;
  DateKeyBegin: String;
  DateKeyEnd: String;
  ICK: String;
  //CurrencyRates section
  CR_Root: String;
  CR_Node: String;
  CR_ICT: Boolean;
  CR_ICN: String;
  Code: String;
  Amount: String;
  Value: String;
 end;

 TERIniDataArray = array of TERIniData;

{
Direct rate:
CURR_N -> CURR_D
Reverse rate:
CURR_S -> CURR_N
Transit rate:
CURR_S -> CURR_N -> CURR_D
}
const
 cx_RateSame = 0;
 cx_RateDirect = 1;
 cx_RateReverse = 2;
 cx_RateTransit = 3;
 //
 cx_NativeName = 'NativeName';
 cx_EngName = 'EngName';

var
 cx_currid, cx_currcode: TStrings;
 cx_IniData: TERIniDataArray;

function ConversionType(SrcCode, DstCode, NatCode: String): Integer;
procedure FillCurrList;
//
function AddIniData(Filename: String): Boolean;
//
function XMLCodes(Filename: String; IniData: TERIniData): Boolean;
procedure XMLToCurrencies(Filename: String; IniData: TERIniData);

implementation
uses
 Windows,
 //IniFiles,
 SysUtils,
 io,
 Tokens,
 cx_vars,
 common,
 m_api,
 cx_http,
 cx_XML;

const
 //section names
 cx_CodesSection = 'CurrencyCodes';
 cx_DailySection = 'Daily';
 cx_DynamicSection = 'Dynamic';
 cx_RatesSection = 'CurrencyRates';
 //key names
 cx_Version = 'Version';
 cx_Name = 'Name';
 cx_Native = 'Native';
 cx_ICU = 'InternalCodeUse';
 cx_DateFormat = 'DateFormat';
 cx_URL = 'URL';
 cx_Root = 'Root';
 cx_Node = 'Node';
 cx_ICT = 'InternalCodeType';
 cx_ICN = 'InternalCodeName';
 cx_DateKey = 'DateKey';
 DateKeyBegin = 'DateKeyBegin';
 cx_DateKeyEnd = 'DateKeyEnd';
 cx_ICK = 'InternalCodeKey';
 cx_Code = 'Code';
 cx_Amount = 'Amount';
 cx_Value = 'Value';
 //
 cx_id = 'id';

function ConversionType(SrcCode, DstCode, NatCode: String): Integer;
begin
 if SrcCode=NatCode then
  begin
   if DstCode=NatCode then
     Result:=cx_RateSame
   else
     Result:=cx_RateDirect;
  end
 else
  begin
   if DstCode=NatCode then
     Result:=cx_RateReverse
   else
    begin
     if SrcCode=DstCode then
       Result:=cx_RateSame
     else
       Result:=cx_RateTransit;
    end;
  end;
end;

procedure FillCurrList;
begin
 cx_currcode.Clear;
 cx_currcode.Add('AED');
 cx_currcode.Add('AFN');
 cx_currcode.Add('ALL');
 cx_currcode.Add('AMD');
 cx_currcode.Add('AOA');
 cx_currcode.Add('ARS');
 cx_currcode.Add('AUD');
 cx_currcode.Add('AZM');
 cx_currcode.Add('BDT');
 cx_currcode.Add('BGN');
 cx_currcode.Add('BHD');
 cx_currcode.Add('BIF');
 cx_currcode.Add('BOB');
 cx_currcode.Add('BND');
 cx_currcode.Add('BRL');
 cx_currcode.Add('BWP');
 cx_currcode.Add('BYR');
 cx_currcode.Add('CAD');
 cx_currcode.Add('CDF');
 cx_currcode.Add('CHF');
 cx_currcode.Add('CLP');
 cx_currcode.Add('CNY');
 cx_currcode.Add('COP');
 cx_currcode.Add('CRC');
 cx_currcode.Add('CUP');
 cx_currcode.Add('CYP');
 cx_currcode.Add('CZK');
 cx_currcode.Add('EEK');
 cx_currcode.Add('EGP');
 cx_currcode.Add('ETB');
 cx_currcode.Add('EUR');
 cx_currcode.Add('DJF');
 cx_currcode.Add('DKK');
 cx_currcode.Add('DZD');
 cx_currcode.Add('GBP');
 cx_currcode.Add('GEL');
 cx_currcode.Add('GMD');
 cx_currcode.Add('GHS');
 cx_currcode.Add('GNF');
 cx_currcode.Add('HKD');
 cx_currcode.Add('HRK');
 cx_currcode.Add('HUF');
 cx_currcode.Add('IDR');
 cx_currcode.Add('ILS');
 cx_currcode.Add('INR');
 cx_currcode.Add('IQD');
 cx_currcode.Add('IRR');
 cx_currcode.Add('ISK');
 cx_currcode.Add('JOD');
 cx_currcode.Add('JPY');
 cx_currcode.Add('KES');
 cx_currcode.Add('KGS');
 cx_currcode.Add('KHR');
 cx_currcode.Add('KPW');
 cx_currcode.Add('KRW');
 cx_currcode.Add('KWD');
 cx_currcode.Add('KZT');
 cx_currcode.Add('LAK');
 cx_currcode.Add('LBP');
 cx_currcode.Add('LKR');
 cx_currcode.Add('LTL');
 cx_currcode.Add('LVL');
 cx_currcode.Add('LYD');
 cx_currcode.Add('MAD');
 cx_currcode.Add('MDL');
 cx_currcode.Add('MGF');
 cx_currcode.Add('MKD');
 cx_currcode.Add('MNT');
 cx_currcode.Add('MRO');
 cx_currcode.Add('MTL');
 cx_currcode.Add('MUR');
 cx_currcode.Add('MWK');
 cx_currcode.Add('MXN');
 cx_currcode.Add('MYR');
 cx_currcode.Add('MZM');
 cx_currcode.Add('NGN');
 cx_currcode.Add('NIO');
 cx_currcode.Add('NOK');
 cx_currcode.Add('NPR');
 cx_currcode.Add('NZD');
 cx_currcode.Add('OMR');
 cx_currcode.Add('PEN');
 cx_currcode.Add('PHP');
 cx_currcode.Add('PKR');
 cx_currcode.Add('PLN');
 cx_currcode.Add('PYG');
 cx_currcode.Add('QAR');
 cx_currcode.Add('ROL');
 cx_currcode.Add('RUR');
 cx_currcode.Add('SAR');
 cx_currcode.Add('SCR');
 cx_currcode.Add('SDG');
 cx_currcode.Add('SEK');
 cx_currcode.Add('SGD');
 cx_currcode.Add('SIT');
 cx_currcode.Add('SKK');
 cx_currcode.Add('SLL');
 cx_currcode.Add('SOS');
 cx_currcode.Add('SRG');
 cx_currcode.Add('SYP');
 cx_currcode.Add('SZL');
 cx_currcode.Add('THB');
 cx_currcode.Add('TJS');
 cx_currcode.Add('TMM');
 cx_currcode.Add('TND');
 cx_currcode.Add('TRL');
 cx_currcode.Add('TWD');
 cx_currcode.Add('TZS');
 cx_currcode.Add('UAH');
 cx_currcode.Add('UGX');
 cx_currcode.Add('USD');
 cx_currcode.Add('UYU');
 cx_currcode.Add('UZS');
 cx_currcode.Add('VEF');
 cx_currcode.Add('VND');
 cx_currcode.Add('XDR');
 cx_currcode.Add('XAF');
 cx_currcode.Add('YER');
 cx_currcode.Add('YUN');
 cx_currcode.Add('ZAR');
 cx_currcode.Add('ZMK');
 cx_currcode.Add('ZWD');
end;

//wrappers
{
function IniReadIntZ(const szFilename: PAnsiChar; const szSection: PAnsiChar; const szKey: PAnsiChar; Default: Integer = 0): Integer;
begin
 Result:=GetPrivateProfileInt(szSection, szKey, Default, szFilename);
end;

function IniReadStringZ(const szFilename: PAnsiChar; const szSection: PAnsiChar; const szKey: PAnsiChar; szDefault: PAnsiChar = nil): String;
 var
  atext: array[0..1023] of Char;
begin
 ZeroMemory(@atext, SizeOf(atext));
 GetPrivateProfileString(szSection, szKey, szDefault, atext, SizeOf(atext), szFilename);
 Result:=atext;
end;

procedure IniWriteStringZ(const szFilename: PAnsiChar; const szSection: PAnsiChar; const szKey: PAnsiChar; szValue: PAnsiChar = nil);
begin
 WritePrivateProfileString(szSection, szKey, szValue, szFilename);
end;
}
function IniReadInt(const sFilename: String; const sSection: String; const sKey: String; Default: Integer = 0): Integer;
begin
 Result:=GetPrivateProfileInt(@sSection[1], @sKey[1], Default, @sFilename[1]);
end;

function IniReadString(const sFilename: String; const sSection: String; const sKey: String; sDefault: String = ''): String;
 var
  atext: array[0..1023] of Char;
begin
 ZeroMemory(@atext, SizeOf(atext));
 if sDefault='' then
   GetPrivateProfileString(@sSection[1], @sKey[1], nil, atext, SizeOf(atext), @sFilename[1])
 else
   GetPrivateProfileString(@sSection[1], @sKey[1], @sDefault[1], atext, SizeOf(atext), @sFilename[1]);
 Result:=atext;
end;

procedure IniWriteString(const sFilename: String; const sSection: String; const sKey: String; sValue: String = '');
begin
 if sValue='' then
   WritePrivateProfileString(@sSection[1], @sKey[1], nil, @sFilename[1])
 else
   WritePrivateProfileString(@sSection[1], @sKey[1], @sValue[1], @sFilename[1]);
end;

function AddIniData(Filename: String): Boolean;
 var
  i: Integer;
  //Ini: TIniFile;
begin
 Result:=FileExists(Filename);
 if Result then
  begin
   //Ini:=TIniFile.Create(Filename);
   SetLength(cx_IniData, Length(cx_IniData)+1);
   i:=High(cx_IniData);
   with cx_IniData[i] do
    begin
     {
     PluginVersion:=Ini.ReadString(piShortName, cx_Version, '');
     SourceName:=Ini.ReadString(piShortName, cx_Name, '');
     NativeCurr:=Ini.ReadString(piShortName, cx_Native, '');
     ICU:=Ini.ReadBool(piShortName, cx_ICU, true);
     DateFormat:=Ini.ReadString(piShortName, cx_DateFormat, '');
     }
     PluginVersion:=IniReadString(Filename, piShortName, cx_Version, '');
     SourceName:=IniReadString(Filename, piShortName, cx_Name, '');
     NativeCurr:=IniReadString(Filename, piShortName, cx_Native, '');
     ICU:=Boolean(IniReadInt(Filename, piShortName, cx_ICU, 0));
     DateFormat:=IniReadString(Filename, piShortName, cx_DateFormat, '');
     //
     {
     CC_URL:=Ini.ReadString(cx_CodesSection, cx_URL, '');
     CC_Root:=Ini.ReadString(cx_CodesSection, cx_Root, '');
     CC_Node:=Ini.ReadString(cx_CodesSection, cx_Node, '');
     CC_ICT:=Ini.ReadBool(cx_CodesSection, cx_ICT, true);
     CC_ICN:=Ini.ReadString(cx_CodesSection, cx_ICN, '');
     NativeName:=Ini.ReadString(cx_CodesSection, cx_NativeName, '');
     EngName:=Ini.ReadString(cx_CodesSection, cx_EngName, '');
     }
     CC_URL:=IniReadString(Filename, cx_CodesSection, cx_URL, '');
     CC_Root:=IniReadString(Filename, cx_CodesSection, cx_Root, '');
     CC_Node:=IniReadString(Filename, cx_CodesSection, cx_Node, '');
     CC_ICT:=Boolean(IniReadInt(Filename, cx_CodesSection, cx_ICT, 0));
     CC_ICN:=IniReadString(Filename, cx_CodesSection, cx_ICN, '');
     NativeName:=IniReadString(Filename, cx_CodesSection, cx_NativeName, '');
     EngName:=IniReadString(Filename, cx_CodesSection, cx_EngName, '');
     //
     {
     D_URL:=Ini.ReadString(cx_DailySection, cx_URL, '');
     DateKey:=Ini.ReadString(cx_DailySection, cx_DateKey, '');
     }
     D_URL:=IniReadString(Filename, cx_DailySection, cx_URL, '');
     DateKey:=IniReadString(Filename, cx_DailySection, cx_DateKey, '');
     //
     {
     Dyn_URL:=Ini.ReadString(cx_DynamicSection, cx_URL, '');
     DateKeyBegin:=Ini.ReadString(cx_DynamicSection, DateKeyBegin, '');
     DateKeyEnd:=Ini.ReadString(cx_DynamicSection, cx_DateKeyEnd, '');
     ICK:=Ini.ReadString(cx_DynamicSection, cx_ICK, '');
     }
     Dyn_URL:=IniReadString(Filename, cx_DynamicSection, cx_URL, '');
     DateKeyBegin:=IniReadString(Filename, cx_DynamicSection, DateKeyBegin, '');
     DateKeyEnd:=IniReadString(Filename, cx_DynamicSection, cx_DateKeyEnd, '');
     ICK:=IniReadString(Filename, cx_DynamicSection, cx_ICK, '');
     //
     {
     CR_Root:=Ini.ReadString(cx_RatesSection, cx_Root, '');
     CR_Node:=Ini.ReadString(cx_RatesSection, cx_Node, '');
     CR_ICT:=Ini.ReadBool(cx_RatesSection, cx_ICT, true);
     CR_ICN:=Ini.ReadString(cx_RatesSection, cx_ICN, '');
     Code:=Ini.ReadString(cx_RatesSection, cx_Code, '');
     Amount:=Ini.ReadString(cx_RatesSection, cx_Amount, '');
     Value:=Ini.ReadString(cx_RatesSection, cx_Value, '');
     }
     CR_Root:=IniReadString(Filename, cx_RatesSection, cx_Root, '');
     CR_Node:=IniReadString(Filename, cx_RatesSection, cx_Node, '');
     CR_ICT:=Boolean(IniReadInt(Filename, cx_RatesSection, cx_ICT, 0));
     CR_ICN:=IniReadString(Filename, cx_RatesSection, cx_ICN, '');
     Code:=IniReadString(Filename, cx_RatesSection, cx_Code, '');
     Amount:=IniReadString(Filename, cx_RatesSection, cx_Amount, '');
     Value:=IniReadString(Filename, cx_RatesSection, cx_Value, '');
    end;
   //FreeAndNil(Ini);
  end;
end;

function XMLCodes(Filename: String; IniData: TERIniData): Boolean;
 var
   s: String;
   f: THANDLE;
begin
 if FileExists(Filename) then
   Result:=true
 else
  begin
   s:=HTTP_NL_Get(IniData.CC_URL);
   f:=ReWrite(PChar(Filename));
   if (f<>INVALID_HANDLE_VALUE) and (f<>0) then
    begin
     BlockWrite(f, s, Length(s));
     CloseHandle(f);
     Result:=true;
    end
   else
     Result:=false;
  end;
end;

procedure XMLToCurrencies(Filename: String; IniData: TERIniData);
 var
  f:THANDLE;
  res: PWideChar;
  i: Integer;
  root, actnode, datanode: HXML;
  tag: WideString;
  s, currdata: String;
begin
 if FileExists(Filename) then
  begin
   i:=GetFSize(PChar(Filename));
   if i=0 then
     exit;
   mGetMem (res ,i+SizeOf(WideChar));
   FillChar(res^,i+SizeOf(WideChar),0);
   f:=Reset(PChar(Filename));
   BlockRead(f,res^,i);
   CloseHandle(f);
   //
   cx_currid.Clear;
   //
   with cx_XMLw do
    begin
     tag:=IniData.CC_Root;
     root:=parseString(ChangeUnicode(res), @i, PWideChar(tag));
     if root<>0 then
      begin
       for i:=0 to getChildCount(root)-1 do
        begin
         tag:=IniData.CC_Node;
         actnode:=getNthChild(root, PWideChar(tag), i);
         currdata:='';
         //
         tag:=IniData.CC_ICN;
         if IniData.CC_ICT then
          begin
           //TO DO: handle ICN as subnode
           //s:=WideCharToString();
          end
         else
          begin
           s:=WideCharToString(getAttrValue(actnode, PWideChar(tag)));
          end;
         currdata:=s;
         //
         tag:=IniData.NativeName;
         datanode:=getChildByPath(actnode, PWideChar(tag), false);
         s:=WideCharToString(getText(datanode));
         currdata:=currdata+'='+EncodeToken(cx_NativeName, s);
         //
         tag:=IniData.EngName;
         datanode:=getChildByPath(actnode, PWideChar(tag), false);
         s:=WideCharToString(getText(datanode));
         currdata:=currdata+EncodeToken(cx_EngName, s);
         //
         cx_currid.Add(currdata);
        end;
      end;
    end;
   //
   mFreeMem(res);
  end;
end;

initialization

 cx_currid:=TStringList.Create;
 cx_currcode:=TStringList.Create;
 SetLength(cx_IniData, 0);

finalization

 SetLength(cx_IniData, 0);
 FreeAndNil(cx_currcode);
 FreeAndNil(cx_currid);

end.
 